
/* author: 黄岩, 
   微薄: 黄岩gg
   微信: huangyan063136
   email: mphyatyh@yeah.net 
   Mobile: 不告诉你
   License: GPLv3 
*/

import {EventUtil} from './ops.js';  

const piece_radius = 15;           //棋子的大小
const interval = 35;               //棋盘各自间距
const nr_line = 15;                //棋盘行数量
const nr_col = 15;                 //棋盘的列数量
const margin = 50;                 //棋盘的边缘距离
const board_color = "#f0c020";    //棋盘的背景颜色
const line_color = "gray";        //棋子边缘线和棋盘线的颜色
const DEPTH = 3; 

var pre_line = -1;          //预落子的位置。 鼠标点击第一次，只是在当前位置画一个棋子
                            //边缘线，并没有真正落子，而是称为预落子。第二次点击到相同
                            //位置才真正落子。为了防止误操作.
var pre_col = -1;           

var next_point = [0, 0];    // 下一个落子的位置

//棋子的颜色
const EMPTY = 0;
const WHITE = 1;
const BLACK = 2;

const BLANK = 0;   // 空
const SELF = 1;    // 自己
const PEER = 2;    // 对方

var g_win = [false, false, false]; 

//0 -- 尚未落子
//1 -- 白子，人
//2 -- 黑子，机器
var board=Array(nr_line);

/* 画一个棋子 
   op==draw，只画边框
   op==fill，画棋子
   op==erase，擦除边框
 */
function draw_piece(line, col, op, color) {
    var draw = document.getElementById("drawing"); 
    if (draw.getContext) {
        var context = draw.getContext("2d"); 
        if (op=="draw") {
            context.beginPath();
            context.arc(margin + col*interval, margin + line*interval, piece_radius, 0, 2*Math.PI);
            context.strokeStyle = line_color; 
            context.stroke();
            context.closePath();
        } else if(op=="fill") {
            context.beginPath();
            context.arc(margin + col*interval, margin + line*interval, piece_radius, 0, 2*Math.PI);
            context.fillStyle = color; 
            context.fill();
            context.closePath();
        } else if(op=="erase") {
            
            var r = piece_radius+1;
            
            /* 用底色填充棋子位置 */
            context.beginPath();
            var save_lw = context.lineWidth;
            context.lineWidth = 0; 
            context.arc(margin + col*interval, margin + line*interval, r, 0, 2*Math.PI);
            context.fillStyle = board_color; 
            context.fill(); 
            context.lineWidth = save_lw; 
            context.closePath();

            /* 重新画棋子位置的棋盘线 */
            context.beginPath();
            var x = margin+col*interval; 
            var y = margin+line*interval;
            context.moveTo(x-r, y); 
            context.lineTo(x+r, y); 
            context.moveTo(x, y-r); 
            context.lineTo(x, y+r); 
            context.strokeStyle = line_color; 
            context.stroke();
            context.closePath();
        }
    }
}

/* 画初始的棋盘 */
function draw_board() {
    var draw = document.getElementById("drawing"); 
    if (draw.getContext) {
        var context = draw.getContext("2d"); 

        /* 填充棋盘底色 */
        context.fillStyle = board_color; 
        var len = (nr_col-1)*interval + 2*margin;
        context.fillRect(0,0,len,len); 

        context.beginPath(); 
        var w = (nr_col-1)*interval;
        var h = (nr_line-1)*interval;

        /* 画横线 */
        for (var i=0; i<nr_line; i++) {
            context.moveTo(margin, margin+i*interval); 
            context.lineTo(margin+w, margin+i*interval); 
        }

        /* 画竖线 */
        for (var i=0; i<nr_col; i++) {
            context.moveTo(margin+i*interval, margin); 
            context.lineTo(margin+i*interval, margin+h); 
        }
        context.strokeStyle = line_color; 
        context.stroke();
        context.closePath();
    }
}

/* 根据鼠标单击位置计算对应的行列坐标 */
function get_coor(pageX, pageY) {
    var line = (pageY - margin) / interval; 
    var col = (pageX - margin) / interval; 
    return {line:Math.floor(line), col:Math.floor(col)}; 
}

/* 这个是相应单击鼠标的函数 */
function on_click(event) {
    var ev = EventUtil.getEvent(event); 
    var coor = get_coor(ev.pageX, ev.pageY); 

    /* 如果点击位置不在棋盘范围内，就什么都不做 */
    if (coor.line<0 || coor.line>=nr_line || coor.col<0 || coor.col>=nr_col) {
        return; 
    }

    /* 点击的位置已经有了棋子，什么都不做 */
    if (board[coor.line][coor.col]!=EMPTY) {
        return; 
    }

    /* 还没有某个位置被预落子 */
    if (pre_line<0) {
        /* 设置为预落子 */
        pre_line = coor.line; 
        pre_col = coor.col; 
        draw_piece(pre_line, pre_col, "draw", null); 
    } else {
        /* 已经有了某个位置被预落子 */
        
        if (pre_line==coor.line && pre_col==coor.col) {
            /* 如果第二次鼠标点击位置与预落子位置相同，则画上这个子 */
            draw_piece(pre_line, pre_col, "fill", "white"); 
            board[pre_line][pre_col] = WHITE; 
            pre_line = -1; 
            pre_col = -1; 
            /* 现在该轮到AI机器人走了 */
            your_turn_AI(coor.line, coor.col); 
        } else {
            /* 如果第二次鼠标点击的位置跟第一次不同，则先删掉预落子，
               再画新的预落子，并更新预落子的位置
            */
            draw_piece(pre_line, pre_col, "erase", null); 
            pre_line = coor.line; 
            pre_col = coor.col; 
            draw_piece(pre_line, pre_col, "draw", null); 
        }
    }
}

/* ============   下面是五子棋的AI实现  ===============*/

/*
    github上的另一个gobang实现
    https://github.com/lihongxun945/gobang

    百度百科的alpha-beta剪枝算法
    https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=Alpha-Beta

    百度百科的置换表和Zobrist哈希算法
    https://baike.baidu.com/item/%E7%BD%AE%E6%8D%A2%E8%A1%A8/19480039?fr=aladdin
    现在的代码中并没有用到这个方法，用的是直接索引。五子棋并不需要对整个盘面进行评估。
*/

/* 获取棋盘上一排格子，dir是四个方向：0-右上到左下的一排、1-一列、2-左上到右下的一排、3-一行 */
function get_row(idx, dir) {
    var d = [[1, -1], [1, 0], [1, 1], [0, 1]]; 
    var ret = []; 
    var i, j, k; 

    //console.log('get_row: idx, dir', idx, dir); 
    if (dir==0) {
        i = idx<nr_col ? 0 : idx-(nr_col-1);
        j = idx<nr_col ? idx : nr_col-1; 
    } else if (dir==1) {
        i = 0; 
        j = idx; 
    } else if (dir==2) {
        i = idx<nr_line ? nr_line-1-idx : 0; 
        j = idx<nr_line ? 0 : idx-(nr_line-1); 
    } else {
        i = idx; 
        j = 0; 
    }
    //console.log('get_row: i, j', i, j); 
    for (k=0; k<nr_line+nr_col; k++) {
        if (i<0||i>=nr_line||j<0||j>=nr_col) {
            break; 
        }
        ret.push(board[i][j]); 
        i += d[dir][0]; 
        j += d[dir][1]; 
    }
    //console.log('ret=', ret.length, ret); 
    return ret; 
}

/* 计算一排格子的得分 */
function get_row_score(row, role) {
    var first=0, last; 
    var blocked_a, blocked_b; 
    var count, score=0; 
    while(first<row.length) {
        if (row[first]!=role) {
            first ++; 
            continue; 
        } 
        last=first; 
        while(last<row.length && row[last]==role) {
            last++; 
        }
        blocked_a = (first==0 || row[first-1]!=EMPTY); 
        blocked_b = (last==row.length-1 || row[last]!=EMPTY); 
        count = last-first; 
        if (count>=5) {
            g_win[role] = true; 
            score += 99999999; 
        } else if (blocked_a && blocked_b) {
                /* do nothing */
        } else if (blocked_a || blocked_b) {
            if (count==4) {         // 死四
                score += 10000; 
            } else if (count==3) {
                score += 1000;      // 死三
            } else if (count==2) {
                score += 100;       // 死二
            } else if (count==1) {
                score += 10;        // 死一
            } else {
                /* do nothing */
            }
        } else {
            if (count==4) {         // 活四
                score += 100000; 
            } else if (count==3) {
                score += 10000;     // 活三
            } else if (count==2) {
                score += 1000;      // 活二
            } else if (count==1) {
                score += 100;       // 活一
            } else {
                /* do nothing */
            }
        }
        first = last; 
    }
    return score; 
}

/* 计算整个盘面的得分 */
function get_role_score(role) {
    var max_idx = [nr_line+nr_col-1, nr_col, nr_line+nr_col-1, nr_line]; 
    var d, i, row, s, score=0; 
    for (d=0; d<4; d++) {
        for (i=0; i<max_idx[d]; i++) {
            row = get_row(i, d); 
            s = get_row_score(row, role); 
            score += s; 
            //console.log('d, i, row, s, score:', d, i, row, s, score); 
        }
    }
    return score; 
}

function get_all_score() {
    return get_role_score(WHITE) - get_role_score(BLACK); 
}

/* 获取随机数 */
function rand() {
    return Math.floor(Math.random()*nr_line); 
}

/* 找出周边是否有棋子，这个函数还有改进的余地，用DP算法会更快 */
function has_neighbor(i, j) {
    var w = 1; 
    var si = i-w>=0 ? i-w : 0; 
    var ei = i+w<nr_line ? i+w : nr_line-1; 
    var sj = j-w>=0 ? j-w : 0; 
    var ej = j+w<nr_col ? j+w : nr_col-1; 
    for (var a=si; a<=ei; a++) {
        for (var b=sj; b<=ej; b++) {
            if (board[a][b]!=0) {
                return true; 
            }
        }
    }
    return false; 
}

/* 找出所有可能合法的位置，也就是棋盘上现有棋子周边的空格 */
function get_legal_pos() {
    var ret = new Array(); 

    for (var i=0; i<nr_line; i++) {
        for (var j=0; j<nr_col; j++) {
            if (board[i][j]!=0) 
                continue; 
            if (has_neighbor(i, j)) {
                ret.push([i, j]); 
            }
        }
    }
    return ret; 
}

function game_over() {
    return g_win[BLACK] || g_win[WHITE]; 
}


function alpha_beta(depth, role, alpha, beta) {
    if (depth==0) {
        var score = get_all_score(); 
        //console.log('return score=', score); 
        return score; 
    }
    var all_pos = get_legal_pos(); 
    for (var p=0; p<all_pos.length; p++) {
        var i, j, next_role; 
        i=all_pos[p][0]; 
        j=all_pos[p][1]; 
        //console.log('depth= '+depth+' line='+i+' col='+j+' role='+role); 
        board[i][j] = role; 
        next_role = (role==BLACK?WHITE:BLACK); 
        var val = -alpha_beta(depth-1, next_role, -beta, -alpha);
        board[i][j] = EMPTY; 
        if (val > alpha) {
            if (depth==DEPTH) {
                //console.log('val', val, 'depth',depth, 'role', role, 'next_role', next_role, 'alpha', alpha, 'beta', beta);
                //console.log(' i=', i, ' j=', j); 
                next_point[0] = i; 
                next_point[1] = j; 
            }
            if (val>beta) {
                return beta; 
            }
            alpha = val; 
        }
    }
    return alpha; 
}

function won(role) {
    g_win[role] = false; 
    get_role_score(role); 
    return g_win[role];
}

function your_turn_AI(h_line, h_col) {
    console.log('your_turn_AI');

    if (won(WHITE)) {
        alert('白赢'); 
        window.location.reload();
    }

    alpha_beta(DEPTH, BLACK, -99999999, 99999999); 

    var line=next_point[0]; 
    var col=next_point[1]; 
    draw_piece(line, col, "fill", "black"); 
    board[line][col] = BLACK; 
    next_point[0] = -1; 
    next_point[1] = -1; 

    if (won(BLACK)) {
        alert('黑赢'); 
        window.location.reload(); 
    }
}


/* ============   下面是初始化代码，在网页装入的时候执行  ===============*/

function init() {
    draw_board(); 
    for (var i=0; i<nr_line; i++) {
        board[i] = new Array(nr_col); 
        for (var j=0; j<nr_col; j++) {
            board[i][j] = EMPTY; 
        }
    }

    var draw = document.getElementById("drawing"); 
    EventUtil.addHandler(draw, "click", on_click); 
}

init();

